/*****************************************
 * @author zhangwq
 * @date   2017.01.20
 * @describe 过滤器，解决跨域权限问题
 ****************************************/

package com.yuqih.common.servlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CrossFilter implements Filter{

	private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList(new String[] { "GET", "POST", "HEAD","DELETE","PUT","OPTIONS"});

	private boolean anyOriginAllowed = false;
	private List<String> allowedOrigins = new ArrayList<String>();
	private List<String> allowedMethods = new ArrayList<String>();
	private List<String> allowedHeaders = new ArrayList<String>();
	private int preflightMaxAge = 0;
	private boolean allowCredentials = true;

	public void init(FilterConfig config) throws ServletException{
		String allowedOriginsConfig = config.getInitParameter("allowedOrigins");
		if (allowedOriginsConfig == null)
			allowedOriginsConfig = "*";
		String[] allowedOrigins = allowedOriginsConfig.split(",");
		for (String allowedOrigin : allowedOrigins){
			allowedOrigin = allowedOrigin.trim();
			if (allowedOrigin.length() <= 0)
				continue;
			if ("*".equals(allowedOrigin)){
				this.anyOriginAllowed = true;
				this.allowedOrigins.clear();
				break;
			}
			this.allowedOrigins.add(allowedOrigin);
		}
		String allowedMethodsConfig = config.getInitParameter("allowedMethods");
		if (allowedMethodsConfig == null)
			allowedMethodsConfig = "GET,POST,DELETE,PUT";
//			allowedMethodsConfig = "GET,POST,DELETE,PUT,OPTIONS";
		this.allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(",")));
		String allowedHeadersConfig = config.getInitParameter("allowedHeaders");
		if (allowedHeadersConfig == null)
			allowedHeadersConfig = "X-Requested-With";
		this.allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(",")));

		String preflightMaxAgeConfig = config.getInitParameter("preflightMaxAge");
		if (preflightMaxAgeConfig == null)
			preflightMaxAgeConfig = "1800";
		try{
			this.preflightMaxAge = Integer.parseInt(preflightMaxAgeConfig);
		}catch (NumberFormatException x){
			x.printStackTrace();
		}
		String allowedCredentialsConfig = config.getInitParameter("allowCredentials");
		if (allowedCredentialsConfig == null)
			allowedCredentialsConfig = "false";
		this.allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig);
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{
		handle((HttpServletRequest)request, (HttpServletResponse)response, chain);
	}

	private void handle(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException{
		String origin = request.getHeader("Origin");
//		String url =request.getRequestURI();
//		request.getRemoteHost();
//		String method =request.getMethod();
		response.setHeader("Access-Control-Allow-Methods", commify(this.allowedMethods));
		//response.setHeader("Access-Control-Allow-Credentials", "true");
//		response.addCookie(new Cookie("remoteIp", request.getRemoteHost()));
		if (origin != null){
			if (originMatches(origin)){
				if (isSimpleRequest(request)){
						handleSimpleResponse(request, response, origin);
						chain.doFilter(request, response);
				}else{
					//只对Options起作用
					handlePreflightResponse(request, response, origin);
				}
			}
		}else {
			chain.doFilter(request, response);
		}
	}

	private boolean originMatches(String origin){
		if (this.anyOriginAllowed)
			return true;
		for (String allowedOrigin : this.allowedOrigins){
			if (allowedOrigin.equals(origin))
				return true;
		}
		return false;
	}

	private boolean isSimpleRequest(HttpServletRequest request){
		String method = request.getMethod();
		if (SIMPLE_HTTP_METHODS.contains(method)){
			return (request.getHeader("Access-Control-Request-Method") == null);
		}
		return false;
	}

	private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin){
		response.setHeader("Access-Control-Allow-Origin", origin);
		if (!(this.allowCredentials))
			return; 
		response.setHeader("Access-Control-Allow-Credentials", "true");
	}

	private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin){
	    boolean methodAllowed = isMethodAllowed(request);
	    if (!(methodAllowed))
	    	return;
	    boolean headersAllowed = areHeadersAllowed(request);
	    if (!(headersAllowed))
    		return;
	    response.setHeader("Access-Control-Allow-Origin", origin);
	    if (this.allowCredentials) 
	    	response.setHeader("Access-Control-Allow-Credentials", "true");
	    if (this.preflightMaxAge > 0) 
	    	response.setHeader("Access-Control-Max-Age", String.valueOf(this.preflightMaxAge));
	    response.setHeader("Access-Control-Allow-Methods", commify(this.allowedMethods));
	    response.setHeader("Access-Control-Allow-Headers", commify(this.allowedHeaders));
	}

	private boolean isMethodAllowed(HttpServletRequest request){
	    String accessControlRequestMethod = request.getHeader("Access-Control-Request-Method");
	    boolean result = false;
	    if (accessControlRequestMethod != null){
	    	result = this.allowedMethods.contains(accessControlRequestMethod);
	    }
	    return result;
	}

  	private boolean areHeadersAllowed(HttpServletRequest request){
	    String accessControlRequestHeaders = request.getHeader("Access-Control-Request-Headers");
	    boolean result = false;
	    if (accessControlRequestHeaders != null){
	    	result = true;
	    	String[] headers = accessControlRequestHeaders.split(",");
	    	for (String header : headers){
    			for (String allowedHeader : this.allowedHeaders){
					if (!(header.trim().equalsIgnoreCase(allowedHeader)))
						continue;
					result = true;
					break;
				}
	    	}
	    }
	    return result;
  	}

	private String commify(List<String> strings){
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < strings.size(); ++i){
			if (i > 0)
				builder.append(",");
			String string = (String)strings.get(i);
			builder.append(string);
		}
		return builder.toString();
	}

	public void destroy(){
		this.anyOriginAllowed = false;
		this.allowedOrigins.clear();
		this.allowedMethods.clear();
		this.allowedHeaders.clear();
		this.preflightMaxAge = 0;
		this.allowCredentials = false;
	}
}
